#ifndef INC_MODEL_FRAGMENT_LINK_BUILDER_H
#define INC_MODEL_FRAGMENT_LINK_BUILDER_H

#include "Fragment.h"
#include "ModelException.h"
#include "Atom_Link.h"

#include <string>
#include <list>
#include <map>


class Link_Prototipe 
{
	const std::string atom1_fqn;
	const std::string atom2_fqn;

	public:
		Link_Prototipe(const std::string& atom1_fqn, const std::string& atom2_fqn)
					: atom1_fqn(atom1_fqn), atom2_fqn(atom2_fqn)
		{}

		const std::string& get_atom1_fqn() const { return atom1_fqn; };
		const std::string& get_atom2_fqn() const { return atom2_fqn; };
		const Atom_Link* new_link(const Atom* a1, const Atom* a2) const { return new Atom_Link(); }
};

class FragmentLinkBuilder
{
	typedef std::string AA_Name; // TODO: Use this typedef from frag and all across the project
	typedef std::list< Link_Prototipe* > Link_Prototipes;
	typedef std::map< AA_Name, Link_Prototipes > Link_Protos_by_Fragment;
	Link_Protos_by_Fragment link_protos;

	public:
		FragmentLinkBuilder()
		{
			// TODO Move to a config file
			Link_Prototipes& ala_n = link_protos["ALA-N"];
			ala_n.push_back(new Link_Prototipe("ALA-N:N",  "ALA-N:1H"));
			ala_n.push_back(new Link_Prototipe("ALA-N:N",  "ALA-N:2H"));
			ala_n.push_back(new Link_Prototipe("ALA-N:N",  "ALA-N:3H"));
			ala_n.push_back(new Link_Prototipe("ALA-N:N",  "ALA-N:CA"));
			ala_n.push_back(new Link_Prototipe("ALA-N:CA", "ALA-N:HA"));
			ala_n.push_back(new Link_Prototipe("ALA-N:CA", "ALA-N:CB"));
			ala_n.push_back(new Link_Prototipe("ALA-N:CA", "ALA-N:C"));
			ala_n.push_back(new Link_Prototipe("ALA-N:CB", "ALA-N:1HB"));
			ala_n.push_back(new Link_Prototipe("ALA-N:CB", "ALA-N:2HB"));
			ala_n.push_back(new Link_Prototipe("ALA-N:CB", "ALA-N:3HB"));
			ala_n.push_back(new Link_Prototipe("ALA-N:C",  "ALA-N:O"));
			Link_Prototipes& ala = link_protos["ALA"];
			ala.push_back(new Link_Prototipe("ALA:N", "ALA:H"));
			ala.push_back(new Link_Prototipe("ALA:N", "ALA:C"));
			ala.push_back(new Link_Prototipe("ALA:C", "ALA:CA"));
			ala.push_back(new Link_Prototipe("ALA:C", "ALA:CB"));
			ala.push_back(new Link_Prototipe("ALA:O", "ALA:C"));
			ala.push_back(new Link_Prototipe("ALA:CB", "ALA:1HB"));
			ala.push_back(new Link_Prototipe("ALA:CB", "ALA:2HB"));
			ala.push_back(new Link_Prototipe("ALA:CB", "ALA:3HB"));
			ala.push_back(new Link_Prototipe("ALA:HA", "ALA:CA"));
			Link_Prototipes& ser = link_protos["SER"];
			ser.push_back(new Link_Prototipe("SER:N", "SER:CA"));
			ser.push_back(new Link_Prototipe("SER:CA", "SER:C"));
			ser.push_back(new Link_Prototipe("SER:C", "SER:H"));
			ser.push_back(new Link_Prototipe("SER:H", "SER:O"));
			ser.push_back(new Link_Prototipe("SER:O", "SER:1HB"));
			ser.push_back(new Link_Prototipe("SER:1HB", "SER:2HB"));
			ser.push_back(new Link_Prototipe("SER:2HB", "SER:CB"));
			ser.push_back(new Link_Prototipe("SER:CB", "SER:HA"));
			ser.push_back(new Link_Prototipe("SER:HA", "SER:HG"));
			ser.push_back(new Link_Prototipe("SER:HG", "SER:OG"));
			ser.push_back(new Link_Prototipe("SER:OG", "SER:HA"));
			Link_Prototipes& ser_c = link_protos["SER-C"];
			ser_c.push_back(new Link_Prototipe("SER-C:N", "SER-C:CA"));
			ser_c.push_back(new Link_Prototipe("SER-C:CA", "SER-C:C"));
			ser_c.push_back(new Link_Prototipe("SER-C:C", "SER-C:H"));
			ser_c.push_back(new Link_Prototipe("SER-C:H", "SER-C:1HB"));
			ser_c.push_back(new Link_Prototipe("SER-C:1HB", "SER-C:2HB"));
			ser_c.push_back(new Link_Prototipe("SER-C:2HB", "SER-C:CB"));
			ser_c.push_back(new Link_Prototipe("SER-C:CB", "SER-C:HA"));
			ser_c.push_back(new Link_Prototipe("SER-C:HA", "SER-C:HG"));
			ser_c.push_back(new Link_Prototipe("SER-C:HG", "SER-C:O"));
			ser_c.push_back(new Link_Prototipe("SER-C:O", "SER-C:OG"));
			ser_c.push_back(new Link_Prototipe("SER-C:OG", "SER-C:OXT"));
			ser_c.push_back(new Link_Prototipe("SER-C:OXT", "SER-C:O"));
		}

		void build_links(Fragment* frag, Molecule* molecule) const
		{
			Link_Protos_by_Fragment::const_iterator map_it = link_protos.find( frag->get_name() );

			if (map_it == link_protos.end() || map_it->second.size() == 0)
				throw ModelException(("Fragment " + frag->get_name() + " has no linking rules defined.").c_str());

			const Link_Prototipes& protos = map_it->second;
			for (Link_Prototipes::const_iterator i = protos.begin();
							i != protos.end(); ++i)
			{
				const Link_Prototipe* lp = *i;

				const std::string& atom1_fqn = lp->get_atom1_fqn();
				const Atom* atom1 = frag->locate( atom1_fqn );

				if (atom1 == NULL)
					throw ModelException(("Atom " + atom1_fqn + " was not found in "
												 "fragment " + frag->get_name() + " yet a "
												 "linking rule refers to it").c_str());

				const std::string& atom2_fqn = lp->get_atom2_fqn();
				const Atom* atom2 = frag->locate( atom2_fqn );

				if (atom2 == NULL)
					throw ModelException(("Atom " + atom2_fqn + " was not found in "
												 "fragment " + frag->get_name() + " yet a "
												 "linking rule refers to it").c_str());

				const Atom_Link* link = lp->new_link(atom1, atom2);
				molecule->add_link( link );
			}
		}
};


#endif /* INC_MODEL_FRAGMENT_LINK_BUILDER_H */
